
package dovs.phases;

import dovs.*;
import dovs.analysis.*;
import dovs.node.*;
import static dovs.Util.*;

import java.util.*;

/**
 * Compiler phase to resolve class hierarchy relationships, inheritance and
 * hiding of fields and inheritance, overriding and hiding of methods according
 * to chapters 8 and 9 of the JLS.
 */
public aspect Hierarchy extends DepthFirstAdapter {
	/**
	 * Look up a method by name in a class or interface, taking inheritance into
	 * account.
	 * 
	 * @param host
	 *            the receiver type
	 * @param id
	 *            the name of the method
	 * @return all methods of the given name that are declared by or inherited
	 *         by the given receiver type.
	 */
	public static Set<AMethodDecl> lookupMethod(PTypeDecl host, TIdentifier id) {
		// TODO
		return Collections.emptySet();
	}

	/**
	 * Look up a field by name in a class or interface, taking inheritance into
	 * account.
	 * 
	 * @param host
	 *            the receiver type
	 * @param id
	 *            the name of the field
	 * @return the field declared by or inherited by the given receiver type, or
	 *         <code>null</code> if no such field exists.
	 */
 	public static AFieldDecl lookupField(PTypeDecl host, TIdentifier id) {
		// TODO
		return null;
	}

	
	/**
	 * Query the type hierarchy relation.
	 * 
	 * @param super_type
	 *            the possible supertype
	 * @param sub_type
	 *            the possible subtype
	 * @return <code>true</code> if <code>super_type</code> is a supertype
	 *         of <code>sub_type</code>, including <code>sub_type</code>
	 *         itself.
	 */
	public static boolean isSuperType(ANamedType super_type, ANamedType sub_type) {
		// TODO 
		return false;
	}

	
	// /////////////////////////////////////////////////////////////////////////
	// ERROR MESSAGES
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Reports the error that a class or interface depends on itself, i.e.
	 * circular inheritance.
	 * 
	 * @param node
	 *            the type declaration
	 * @param fatal
	 *            if {@code true} the compilation is stopped immediately
	 */
	@SuppressWarnings("unused")
	private static void errorCircularInheritance(PTypeDecl node, boolean fatal) {
		Errors.error(ErrorType.CIRCULAR_INHERITANCE, node.getName(),
				"Circular inheritance", fatal);
	}
	
	/**
	 * Report the error that a class that has abstract methods must itself be
	 * abstract.
	 * 
	 * @param node
	 *            the class declaration
	 * @param method
	 *            the method declaration of the abstract method
	 */
	@SuppressWarnings("unused")
	private static void errorClassMustBeAbstract(AClassTypeDecl node, 
			AMethodDecl method) {
		Errors.error(ErrorType.CLASS_MUST_BE_ABSTRACT, node.getName(), 
				"The class " + node.getName().getText() + 
				" must be declared abstract because of abstract method " + 
				Util.getMethodSignatureString(method), false);
	}
	
	/**
	 * Reports the error that a class must not contain two different methods
	 * with the same signature but different return types.
	 * 
	 * @param currentType
	 *            the current type declaration
	 * @param replacingMethod
	 *            the replacing method declaration
	 * @param replaceMethod
	 *            the replaced method declaration
	 */
	@SuppressWarnings("unused")
	private static void errorDifferentReturnType(PTypeDecl currentType, 
			AMethodDecl replacingMethod, AMethodDecl replacedMethod) {
		boolean replace = !(replacingMethod.isAbstract() && replacedMethod.isAbstract());
		boolean override = !(replacingMethod.isStatic());
		Errors.error(
				ErrorType.DIFFERENT_RETURN_TYPE, 
				currentType.getName(), 
				"The method " + Util.getMethodSignatureString(replacingMethod) + 
				" must have same return type as " + 
				(replace ? (override ? "overridden" : "hidden") : "other inherited") + 
				" method " + Util.getMethodSignatureString(replacedMethod),
				false
		);
	}
	
	/**
	 * Reports the error that a class must not declare two constructors with the
	 * same signature.
	 * 
	 * @param node
	 *            one of the constructor declarations
	 */
	@SuppressWarnings("unused")
	private static void errorDuplicateConstructor(AConstructorDecl node) {
		Errors.error(ErrorType.DUPLICATE_CONSTRUCTOR,
				node.getName(), 
				"Duplicate constructor",
				false
		);
	}
	
	/**
	 * Reports the error that a class must not declare two methods with the same
	 * signature.
	 * 
	 * @param node
	 *            one of the method declarations
	 */
	@SuppressWarnings("unused")
	private static void errorDuplicateMethod(AMethodDecl node) {
		Errors.error(ErrorType.DUPLICATE_METHOD, 
				node.getName(), "The method " + Util.getMethodSignatureString(node) + 
				" is already declared", false);
	}
	
	/**
	 * Reports the error that a class cannot extend a final class.
	 * 
	 * @param node
	 *            the class declaration
	 */
	@SuppressWarnings("unused")
	private static void errorExtendsFinalClass(AClassTypeDecl node) {
		Errors.error(ErrorType.EXTENDS_FINAL_CLASS, node.getName(), 
				"The class " + node.getName().getText() + 
				" cannot extend a final class", false);
	}
	
	/**
	 * Reports the error that a class cannot extend a non-class
	 * 
	 * @param node
	 *            the named type of the non-class
	 */
	@SuppressWarnings("unused")
	private static void errorExtendsNonClass(ANamedType node) {
		Errors.error(ErrorType.EXTENDS_NON_CLASS, node.getName().getToken(), 
				node.typeName() + " is not a class", false);
	}
	
	/**
	 * Reports the error that a replacing method cannot declare an checked
	 * exception in its throws clause which is not declared by the replaced
	 * method.
	 * 
	 * @param methodDecl
	 *            the method declaration
	 * @param exception
	 *            the named type of the exception
	 */
	@SuppressWarnings("unused")
	private static void errorIllegalThrowsInReplace(PTypeDecl currentType, 
			AMethodDecl methodDecl, ANamedType exception) 
	{
		boolean override = !(methodDecl.isStatic());
		Errors.error(ErrorType.ILLEGAL_THROWS_IN_REPLACE, currentType.getName(), 
				"The method " + Util.getMethodSignatureString(methodDecl) + 
				" declares checked exception " + exception.typeName() + 
				" which is not declared by " + 
				(override ? "overridden" : "hidden") + " method", false);
	}
	
	/**
	 * Reports the error that a class cannot implement a non-interface.
	 * 
	 * @param node
	 *            the named type of the non-interface
	 */
	@SuppressWarnings("unused")
	private static void errorImplementsNonInterface(ANamedType node) {
		Errors.error(ErrorType.IMPLEMENTS_NON_INTERFACE, node.getName().getToken(), 
				node.typeName() + " is not an interface", false);
	}
	/**
	 * Reports the error that two different inherited fields clash.
	 * 
	 * @param currentType
	 *            the current type declaration
	 * @param fieldDecl
	 *            one of the field declarations
	 */
	@SuppressWarnings("unused")
	private static void errorInheritedFieldClash(PTypeDecl currentType, 
			AFieldDecl fieldDecl) {
		Errors.error(ErrorType.INHERITED_FIELD_CLASH, currentType.getName(), 
				"Multiply inherited field name " + fieldDecl.getName().getText(),
				false);
	}
	
	/**
	 * Reports the error that a nonstatic method cannot replace a static method.
	 * 
	 * @param methodDecl
	 *            the method declaration of the nonstatic method
	 */
	@SuppressWarnings("unused")
	private static void errorNonstaticReplaceStatic(AMethodDecl methodDecl) {
		Errors.error(ErrorType.NONSTATIC_REPLACE_STATIC, methodDecl.getName(), 
				"The nonstatic method " + Util.getMethodSignatureString(methodDecl) + 
				" cannot replace a static method", false);
	}
	
	/**
	 * Reports the error that a method cannot replace a final method.
	 * 
	 * @param node
	 *            the method declaration of the replacing method
	 */
	@SuppressWarnings("unused")
	private static void errorReplaceFinal(AMethodDecl node) {
		boolean override = !(node.isStatic());
		Errors.error(ErrorType.REPLACE_FINAL, node.getName(), "The method " + 
				Util.getMethodSignatureString(node) + " cannot " + 
				(override ? "override" : "hide") + " a final method", false);
	}

	/**
	 * Reports the error that a protected method cannot replace a public method.
	 * 
	 * @param currentType
	 *            the current type declaration
	 * @param methodDecl
	 *            the method declaration of the protected method
	 */
	@SuppressWarnings("unused")
	private static void errorProtectedReplacePublic(PTypeDecl currentType, 
			AMethodDecl methodDecl) {
		boolean override = !(methodDecl.isStatic());
		Errors.error(ErrorType.PROTECTED_REPLACE_PUBLIC, currentType.getName(), 
				"The method " + Util.getMethodSignatureString(methodDecl) + 
				" cannot have stricter access privileges than " + 
				(override ? "overridden" : "hidden") + " method", false);
	}
	
	/**
	 * Reports the error that the same interface is found twice in the
	 * implements clause of a class or in the extends clause of an interface.
	 * 
	 * @param type
	 *            the named type of the interface
	 */
	@SuppressWarnings("unused")
	private static void errorRepeatedInterface(ANamedType type) {
		Errors.error(ErrorType.REPEATED_INTERFACE, type.getName().getToken(), 
				"Repeated interface " + type.typeName(), false);
	}
	
	/**
	 * Reports the error that a static method cannot replace a nonstatic method.
	 * 
	 * @param method
	 *            the method declaration of the static method
	 */
	@SuppressWarnings("unused")
	private static void errorStaticReplaceNonstatic(AMethodDecl method) {
		Errors.error(ErrorType.STATIC_REPLACE_NONSTATIC, method.getName(),
				"The static method " + Util.getMethodSignatureString(method) + 
				" cannot hide a nonstatic method", false);
	}
}
